/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include "kprintf.h"
/* includes (standard library, system) ---------------------------------------*/
#include <stdint.h>
#include <stdarg.h>
#include <stddef.h>
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_printf.h>
#include <x_warn.h>
#include <x_module.h>

#ifdef CONFIG_SYSCALL
#include <x_syscall.h>
#endif

#include <hal_uart.h>

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/
/* variables (global) --------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

static struct hal_uart *__kprintf_uart = NULL;

/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

/**
 * platform or bsp should define there own early print routine to override this
 * kprintf output strings like this: kprintf -> xprintf -> __uart_putc
 * __uart_putc depends on the hal_uart, but hal_uart is only usable after system
 * up, so kprintf can not be used before uart driver initialization, so add a
 * weak symbol early_putc to output before hal_uart usable, soc or bsp can
 * implement such a routine (usually in poll mode)
 */
__weak int early_putc(unsigned char ch)
{
    return 0;
}

static int __uart_putc(unsigned char ch)
{
    if (__kprintf_uart == NULL) {
        return early_putc(ch);
    }

    return sleepable() ? (int)hal_uart_putc(__kprintf_uart, ch)
                       : (int)hal_uart_poll_putc(__kprintf_uart, ch);
}

static int __kprintf_putc(uintptr_t arg, unsigned char ch)
{
#ifdef CONFIG_KPRINTF_OUT_CR
    if (ch == '\n') {
        __uart_putc('\r');
    }
#endif

    __uart_putc(ch);

    return 1;
}

int kprintf(const char *format, ...)
{
    va_list valist;
    int nbytes;

    va_start(valist, format);
    nbytes = xprintf(format, valist, __kprintf_putc, 0);
    va_end(valist);

    return nbytes;
}

#ifndef CONFIG_SOC_VM
__unused int _write(int fd, char *ptr, int len)
{
    for (int i = 0; i < len; ++i) {
        __kprintf_putc(0, ptr[i]);
    }
    return len;
}
#endif

int puts(const char *str)
{
    kprintf(str);

    return 0;
}

/**
 * kprintf_lib_init - kprintf library initialization routine
 *
 * return : 0 on success, negative value on error
 */
static int kprintf_lib_init(void)
{
    __kprintf_uart = hal_uart_open(CONFIG_KPRINTF_UART_NAME);

    WARN_ON(__kprintf_uart == NULL, return -1, "Fail to open uart for kprintf!");

    return 0;
}

MODULE_INIT(postdriver, kprintf_lib_init);

#ifdef CONFIG_SYSCALL
static const uintptr_t syscall_entries_kprintf[] = {
    (uintptr_t)kprintf,
};

/* must be global to override weak symbol */

const struct syscall_table syscall_table_kprintf = {
    ARRAY_SIZE(syscall_entries_kprintf),
    syscall_entries_kprintf,
};
#endif
